#include "danpianji.h"

enum {
    GET_HEAD,
    GET_STA,
    GET_ETX,
    GET_CMD,
    GET_EOT,
    GET_DATATYPE,
    GET_CRC,
    GET_DATAADDR,
    GET_DATANUM,
    GET_DATA,
    GET_ERRORCODE,
    DPJ_ERROR,
    ERROR1,
    ERROR3,
    ERROR4,
};
#define ENQ 5
#define ACK 6
#define NCK 0x15
#define EOT 4
#define ETX 3

#include <plc_rte_abi.h>

extern "C" void danpianji_thread(const void *arg) {
    auto *dev = (danpianji *) arg;
    dev->thread();
}

int danpianji::init(u8 serialNo) {
    buf_data_init(&rx_buf, _rxbuf, 64);
    this->serialNo = serialNo;
    request_nrs = 2;
    plc_rte->create_thread("danpianji", 256, 0, (void *) &danpianji_thread, this);
    return 0;
}

void danpianji::thread() {
    plc_rte->SerialBegin(serialNo, 4800);
    while (1) {
        for (int i = 0; i < request_nrs; i++) {
            if (request[i].enable) {
                request[i].last_tick = plc_rte->timestamp();
                while (plc_rte->timestamp() < request[i].last_tick + request[i].period) {
                    plc_rte->delay(1);
                    // continue;
                }
                query(&request[i]);
            }
        }
        plc_rte->delay(10);
    }
}

int danpianji::query(request_t *req) {
    if (hand(req) == 0) {
        plc_rte->delay(1000);
        if (read(req) == 0) {
            req->okcount++;
        } else
            req->errcount++;
    } else {
        req->errcount++;
    }
    return 0;
}

int danpianji::hand(request_t *req) {
    u8 buf[4];
    buf[0] = ENQ;
    buf[1] = req->id;
    buf[2] = EOT;
    buf[3] = calc_xor(buf, 3);
    plc_rte->SerialWrite(serialNo, buf, (u8) 4);
    last_tick = plc_rte->timestamp();
    state = GET_HEAD;
    int r = 1;
    buf_data_clear(&rx_buf);
    while (plc_rte->timestamp() - last_tick < 1000 && r != 0) {
        if (plc_rte->SerialAvailable(serialNo)) {
            auto c = (u8) plc_rte->SerialRead(serialNo);
            buf_data_put_char(&rx_buf, c);
            r = process_hand(c, req);
            if (r <= 0)
                return r;
        }
        plc_rte->delay(1);
    }
    return r;
}

int danpianji::read(request_t *req) {
    u8 buf[8];
    buf[0] = ENQ;
    buf[1] = 'R';
    buf[2] = 2; //Byte
    buf[3] = req->start & 0xff;
    buf[4] = req->start >> 8;
    buf[5] = req->nrs;
    buf[6] = EOT;
    buf[7] = calc_xor(buf, 7);
    plc_rte->SerialWrite(serialNo, buf, 8);
    last_tick = plc_rte->timestamp();
    state = GET_HEAD;
    int r = 1;
    buf_data_clear(&rx_buf);
    while (plc_rte->timestamp() - last_tick < 1000 && r != 0) {
        if (plc_rte->SerialAvailable(serialNo)) {
            auto c = (u8) plc_rte->SerialRead(serialNo);
            buf_data_put_char(&rx_buf, c);
            r = process_read(c, req);
            if (r <= 0)
                return r;
        }
        plc_rte->delay(1);
    }
    return 0;
}

//06 01 03 04
//
int danpianji::process_hand(u8 c, request_t *req) {
    u8 crc;
    if (state != GET_HEAD && plc_rte->timestamp() - last_tick > 30)
        return -2;
    last_tick = plc_rte->timestamp();
    switch (state) {
        case GET_HEAD:
            if (c == ACK || c == NCK) {
                state = GET_STA;
            }
            break;
        case GET_STA:
            if (c == req->id)
                state = GET_ETX;
            else {
                req->status = -DPJ_ERROR;
                return req->status;
            }
            break;
        case GET_ETX:
            if (c == ETX)
                state = GET_CRC;
            else {
                req->status = -ERROR1;
                return req->status;
            }
            break;
        case GET_CRC:
            crc = calc_xor(rx_buf.buf, (u8) buf_data_len(&rx_buf) - 1);
            if (crc != c) {
                req->status = -ERROR3;
                return req->status;
            }
            return 0;
        default:
            break;
    }
    return 1;
}

int danpianji::process_read(u8 c, request_t *req) {
    u8 crc;
    if (state != GET_HEAD && plc_rte->timestamp() - last_tick > 30)
        return -2;
    last_tick = plc_rte->timestamp();
    switch (state) {
        case GET_HEAD:
            if (c == ACK) {
                state = GET_DATANUM;
            } else if (c == NCK) {
                state = GET_ERRORCODE;
            }
            break;
        case GET_DATANUM:
            if (buf_data_len(&rx_buf) == 3) {
                datalen = rx_buf.buf[1] + rx_buf.buf[2] * 256;
                state = GET_DATA;
            }
            break;
        case GET_DATA:
            if (buf_data_len(&rx_buf) >= (3 + datalen)) {
                state = GET_ETX;
            }
            break;
        case GET_ETX:
            if (c == ETX)
                state = GET_CRC;
            else {
                req->status = -ERROR1;
                return req->status;
            }
            break;
        case GET_CRC:
            crc = calc_xor(rx_buf.buf, (u8) buf_data_len(&rx_buf) - 1);
            if (crc != c) {
                req->status = -ERROR3;
                return req->status;
            }
            if (rx_buf.buf[0] == ACK) {
                for (int i = 0; i < datalen; i++) {
                    req->buf[i] = rx_buf.buf[3 + i];
                }
                return 0;
            } else if (rx_buf.buf[0] == NCK) {
                req->status = rx_buf.buf[1];
                return 0;
            }
            break;
        case GET_ERRORCODE:
            state = GET_ETX;
            break;
        default:
            break;
    }
    return 1;
}
